#include <termios.h>
#include <pthread.h>
#include <sys/uio.h>

#include "util.h"

int getmaster()
{
    int fdm = open("/dev/ptmx", O_RDWR | O_NOCTTY);
    if (fdm < 0)
        perror("ptmx open"), exit(1);
    grantpt(fdm);
    unlockpt(fdm);
    return fdm;
}

int getslave(int fdm)
{
    char buf[32];
    int ptyno, fds;
    if (ioctl(fdm, TIOCGPTN, &ptyno))
            perror("TIOCGPTN"), exit(1);
    sprintf(buf, "/dev/pts/%d", ptyno);
    fds = open(buf, O_RDWR | O_NOCTTY);
    if (fds < 0)
        perror("pts open"), exit(1);
    return fds;
}

int setserial(int fd)
{
    struct termios ti;
    int saved_ldisc, ldisc = N_HCI;
    unsigned char flowctl = 0;
    unsigned int speed = B115200;

    if (tcflush(fd, TCIOFLUSH) < 0) {
        perror("Failed to flush serial port");
        close(fd);
        exit(1);
    }

    if (ioctl(fd, TIOCGETD, &saved_ldisc) < 0) {
        perror("Failed get serial line discipline");
        close(fd);
        exit(1);
    }

    /* Switch TTY to raw mode */
    memset(&ti, 0, sizeof(ti));
    cfmakeraw(&ti);

    ti.c_cflag |= (speed | CLOCAL | CREAD);

    if (flowctl) {
        /* Set flow control */
        ti.c_cflag |= CRTSCTS;
    }

    if (tcsetattr(fd, TCSANOW, &ti) < 0) {
        perror("Failed to set serial port settings");
        close(fd);
        exit(1);
    }

    if (ioctl(fd, TIOCSETD, &ldisc) < 0) {
        perror("Failed set serial line discipline");
        close(fd);
        exit(1);
    }

    return 0;
}

int attachproto(int fd)
{
    unsigned int flags = (1 << HCI_UART_RESET_ON_INIT);
    if (ioctl(fd, HCIUARTSETFLAGS, flags) < 0) {// set flags
        perror("Failed to set flags");
        close(fd);
        return -1;
    }

    unsigned int proto = 0;
    if (ioctl(fd, HCIUARTSETPROTO, proto) < 0) {// HCI set protocol
        perror("Failed to set protocol");
        close(fd);
        return -1;
    }

    int dev_id = ioctl(fd, HCIUARTGETDEVICE);
    if (dev_id < 0) {
        perror("Failed to get device id");
        close(fd);
        return -1;
    }
}

void hci_send_event_packet(int fd, uint8_t evt, void* data,
                                  size_t data_len)
{
  struct iovec iv[3];
  struct hci_event_hdr hdr;
  hdr.evt = evt;
  hdr.plen = data_len;
  uint8_t type = HCI_EVENT_PKT;
  iv[0].iov_base = &type;
  iv[0].iov_len = sizeof(type);
  iv[1].iov_base = &hdr;
  iv[1].iov_len = sizeof(hdr);
  iv[2].iov_base = data;
  iv[2].iov_len = data_len;
  if (writev(fd, iv, sizeof(iv) / sizeof(struct iovec)) < 0)
    exit(1);
}

void hci_send_event_cmd_complete(int fd, uint16_t opcode, void* data,
                                        size_t data_len)
{
  struct iovec iv[4];
  struct hci_event_hdr hdr;
  hdr.evt = HCI_EV_CMD_COMPLETE;
  hdr.plen = sizeof(struct hci_ev_cmd_complete) + data_len;
  struct hci_ev_cmd_complete evt_hdr;
  evt_hdr.ncmd = 1;
  evt_hdr.opcode = opcode;
  uint8_t type = HCI_EVENT_PKT;
  iv[0].iov_base = &type;
  iv[0].iov_len = sizeof(type);
  iv[1].iov_base = &hdr;
  iv[1].iov_len = sizeof(hdr);
  iv[2].iov_base = &evt_hdr;
  iv[2].iov_len = sizeof(evt_hdr);
  iv[3].iov_base = data;
  iv[3].iov_len = data_len;
  if (writev(fd, iv, sizeof(iv) / sizeof(struct iovec)) < 0)
    exit(1);
}

bool process_command_pkt(int fd, char* buf, ssize_t buf_size)
{
  struct hci_command_hdr* hdr = (struct hci_command_hdr*)buf;
  if (buf_size < (ssize_t)sizeof(struct hci_command_hdr) ||
      hdr->plen != buf_size - sizeof(struct hci_command_hdr)) {
    exit(1);
  }
  bool retornot = false;

  switch (hdr->opcode) {
  case HCI_OP_WRITE_CA_TIMEOUT: {
    // for bluetoothd program, there is another over command
#ifndef BLUETOOTHD_ENV
    retornot = true;
#endif
    break;
  }
  case HCI_OP_READ_BD_ADDR: {
    struct hci_rp_read_bd_addr rp = {0};
    rp.status = 0;
    memset(&rp.bdaddr, 0xaa, 6);
    hci_send_event_cmd_complete(fd, hdr->opcode, &rp, sizeof(rp));
    return false;
  }
  case HCI_OP_READ_BUFFER_SIZE: {
    struct hci_rp_read_buffer_size rp = {0};
    rp.status = 0;
    rp.acl_mtu = 1021;
    rp.sco_mtu = 96;
    rp.acl_max_pkt = 4;
    rp.sco_max_pkt = 6;
    hci_send_event_cmd_complete(fd, hdr->opcode, &rp, sizeof(rp));
    return false;
  }
  case HCI_OP_WRITE_LOCAL_NAME: {
#ifdef BLUETOOTHD_ENV
    retornot = true;
    break;
#endif
  }
  }
  char dummy[0xf9] = {0};
  hci_send_event_cmd_complete(fd, hdr->opcode, dummy, sizeof(dummy));
  if (!retornot) return false;
  else return true;
}

void *event_thread(void* arg)
{
  int fdmaster = *(int*)arg;
  while (1) {
    char buf[1024] = {0};
    ssize_t buf_size = read(fdmaster, buf, sizeof(buf));
    if (buf_size < 0)
      exit(1);
    if (buf_size > 0 && buf[0] == HCI_COMMAND_PKT) {
      if (process_command_pkt(fdmaster, buf + 1, buf_size - 1))
        break;
    }
  }
  return NULL;
}

struct pparam initialize_hci_uart() 
{
  int master = getmaster();
  int slave = getslave(master);
  setserial(slave);
  attachproto(slave);

  pthread_t th;
  pthread_create(&th, NULL, event_thread, &master);
 
  int hci_sock = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
  int ret = ioctl(hci_sock, HCIDEVUP, 0);

  if (ret) {
    if (ret && errno != EALREADY)
      perror("ioctl(HCIDEVUP) failed"),exit(1);
  }

  pthread_join(th, NULL);  
  struct pparam res;
  res.mfd = master;
  res.sfd = slave;
  res.sock = hci_sock;
  return res;
}

struct pparam1 initialize_tty()
{
    int master = getmaster();
    int slave = getslave(master);
    setserial(slave);

    struct pparam1 result = {master, slave};
    return result;
}

struct pparam2 tty_to_hci(struct pparam1 p)
{
  pthread_t th;
  attachproto(p.sfd);

  pthread_create(&th, NULL, event_thread, &p.mfd);
 
  int hci_sock = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
  int ret = ioctl(hci_sock, HCIDEVUP, 0);

  if (ret) {
    if (ret && errno != EALREADY)
      perror("ioctl(HCIDEVUP) failed"),exit(1);
  }

  pthread_join(th, NULL);  
  struct pparam2 res = {hci_sock};
  return res;
}

struct pparam2 tty_to_hci_hang(struct pparam1 p)
{
  attachproto(p.sfd);
  struct pparam2 res = {-1};
  return res;
}
